home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / Libraries / VideoToolbox 96.06.15 / VideoToolboxSources / GDTestClut.c < prev    next >
Text File  |  1996-03-26  |  19KB  |  572 lines

  1. /*
  2. GDTestClut.c
  3.  
  4. This is used by TimeVideo; I can’t think of any reason for including it in any
  5. other program.
  6.  
  7. OSErr GDTestClut(FILE *o[2],GDHandle device,short flags,VideoInfo *card);
  8.  
  9. GDTestClut.c tests whether the video clut can be written and read faithfully,
  10. and saves the results in a user-supplied VideoInfo structure. The test consists
  11. of writing random numbers to all the clut entries, reading them back, and
  12. comparing. I’ve been surprised to find more than a few video drivers that fail
  13. this test, for various reasons.
  14.  
  15. The bits of “flags” are tested independently. If flags&testClutQuicklyFlag then
  16. SetEntriesQuickly() will be tested instead of GDSetEntries/GDDirectSetEntries.
  17. If flags&testClutSeriallyFlag then the clut entries will be set individually,
  18. calling GDSetEntries once for each clut entry, to check the clut entry
  19. addressing. If flags&testClutLinearFlag then a simple sequence, will be loaded
  20. into the clut, instead of random numbers, to help figuring out systematic
  21. errors. The sequence is (0,0,0),(1,0,0), (0,2,0),(0,0,3),(4,4,4),(4,0,0), and so
  22. on.
  23.  
  24. Returned value is zero if ok, nonzero if error occurred.
  25.  
  26. GDTestClut recognizes the common driver errors and reports them in a sensible
  27. way, using the various fields of the card->depth[d].clut structure. Errors
  28. accumulate in the card->depth[d].clut structure, allowing you to make multiple
  29. calls to GDTestClut and only then summarize the results. It is important that
  30. you zero card->depth[d].clut.tests and card->depth[d].clutQuickly.tests before
  31. your first call to GDTestClut, to induce it to zero the rest of the clut
  32. structures.
  33.  
  34. Assumes that GDevice record is valid, i.e. the user has not called GDSetMode().
  35.  
  36. HISTORY:
  37. 3/9/93    dgp    code extracted from the demo TestCluts.c, to create a reusable
  38.         subroutine.
  39. 3/10/93    dgp    No longer assume that GDevice record reflects the actual state of the
  40.         driver.
  41. 4/5/93    dgp    Add support for grayB.
  42. 4/19/93    dgp    Fixed bug in VisibleHash that used garbage in place of
  43.             linear color table when gdType==directType. Use GDNewLinearColorTable.
  44. 5/11/93    dgp    GDTestClutHash now checks for valid device, in response to bug report
  45.             by Jonathan Brecher.
  46. 5/18/93    dgp    Allow small, 0.001, tolerance in establishing the identity transform.
  47. 5/25/93    dgp    Increased proportion of time spent loading clut in VisibleHash().
  48. 9/5/94 dgp removed assumption in printf's that int==short.
  49. 12/29/94 dgp WriteAndReadClut now waits for vbl before reading.
  50. 4/8/95 dgp I introduced WAIT_FOR_VBL to enable/disable waiting for vbl between 
  51.             write and read of CLUT. I introduce the wait to see if it would 
  52.             help certain video cards pass the tests. However, in both cases
  53.             it made no difference and the explanation turned out to be that
  54.             cscSetGamma wasn't supported. So, in the interest of fast testing,
  55.             I'm omitting the wait.
  56. 3/7/96 dgp Fix program to reflect fact that cscSetEntries reads from table[0],.... 
  57.             whereas cscGetEntries writes to table[start],.... 
  58. 3/26/96 dgp increase tolerance for recognizing identity, allow insignificant bits to be garbage.
  59. */
  60. #include "VideoToolbox.h"
  61. //#include <Errors.h>
  62. #include "GDInfo.h"
  63. #define WAIT_FOR_VBL 0    // 0 for no wait; 1 to wait for vbl between write & read of CLUT.
  64.  
  65. // These functions are solely for use within this file.
  66. ColorSpec *MakeClutTable(GDHandle device,short flags);
  67. OSErr WriteClut(GDHandle device,ColorSpec putTable[],short flags);
  68. OSErr ShowGammaTable(FILE *o[2],GDHandle device);
  69. void RGBToGray(RGBColor *rgb,short dacSize);
  70. Boolean UnequalClutEntry(RGBColor *a,RGBColor *b,short mask);
  71. OSErr VisibleHash(GDHandle device,SetEntriesFunction function,short clutEntries
  72.     ,short *hashPtr);
  73. OSErr EstimateClutTransform(short flags,VideoInfo *card);
  74. OSErr ComputeRMSClutError(FILE *file
  75.     ,ColorSpec *putTable,ColorSpec *getTable,short flags,VideoInfo *card);
  76. OSErr WriteAndReadClut(GDHandle device,short flags,ColorSpec **put,ColorSpec **get);
  77. #define SixteenBitGray 1
  78.  
  79. OSErr GDTestClut(FILE *o[2],short flags,VideoInfo *card)
  80. {
  81.     short i,j,clutSize,quickly,isGray;
  82.     int error;
  83.     ColorSpec *putTable=NULL,*getTable=NULL;
  84.     VideoCardClutTest *clut;
  85.     
  86.     if(card->device==NULL)return 0;
  87.     isGray=!TestDeviceAttribute(card->device,gdDevType);
  88.     for(quickly=0;quickly<2;quickly++){
  89.         clut=&card->depth[card->d].clut[quickly][isGray];
  90.         if(!clut->read.tested){
  91.             for(j=0;j<3;j++)for(i=0;i<3;i++)clut->read.rgbGain[j][i]=NAN;
  92.             for(j=0;j<3;j++)clut->read.rgbError[j]=NAN;
  93.             for(j=0;j<3;j++)clut->read.rgbErrorAtOnce[j]=NAN;
  94.             clut->read.errors=0;
  95.             clut->read.errorsAtOnce=0;
  96.         }
  97.         if(!clut->visual.tested){
  98.             clut->visual.errors=0;
  99.             clut->visual.errorsAtOnce=0;
  100.         }
  101.         if(!clut->hash.tested){
  102.             clut->hash.errors=0;
  103.         }
  104.     }
  105.     quickly=((flags&testClutQuicklyFlag)!=0);
  106.     clut=&card->depth[card->d].clut[quickly][isGray];
  107.     if(!clut->read.doTest)return 0;
  108.     
  109.     GDInfo(card);
  110.     if(card->device==NULL)return 0;
  111.     clutSize=card->depth[card->d].clutSize;
  112.     error=GDSaveGamma(card->device);
  113.     error=GDUncorrectedGamma(card->device);
  114.     if(error)return error;
  115.     
  116.     error=EstimateClutTransform(flags&~testClutSeriallyFlag,card);
  117.     if(error)goto done;
  118.  
  119.     error=WriteAndReadClut(card->device,flags,&putTable,&getTable);
  120.     if(error)goto done;
  121.  
  122.     // COMPARE
  123.     ComputeRMSClutError(o[1],putTable,getTable,flags,card);
  124.     card->clutTested=1;
  125.     
  126.     done:
  127.     DisposePtr((Ptr)putTable);
  128.     DisposePtr((Ptr)getTable);
  129.     GDRestoreGamma(card->device);
  130.     GDRestoreDeviceClut(card->device);
  131.  
  132. //    clut->visual.doTest |= error && clut->hash.doTest;
  133. //    if(clut->visual.doTest)error=GDTestClutVisually(flags,card);
  134.     return error;
  135. }
  136.  
  137. OSErr EstimateClutTransform(short flags,VideoInfo *card)
  138. {
  139.     short i,j,tableSize,quickly,isGray;
  140.     int error=0;
  141.     ColorSpec *putTable,*getTable;
  142.     VideoCardClutTest *clut;
  143.     long putSum[3],getSum[3][3];
  144.     unsigned short *put3,*get3;
  145.         
  146.     error=WriteAndReadClut(card->device,flags|testClutGains,&putTable,&getTable);
  147.     if(error)return error;
  148.     tableSize=GetPtrSize((Ptr)putTable)/sizeof(putTable[0]);
  149.     
  150.     // Estimate transformation
  151.     for(i=0;i<3;i++){
  152.         putSum[i]=0;
  153.         for(j=0;j<3;j++)getSum[i][j]=0;
  154.     }
  155.     for(i=0;i<tableSize;i++){
  156.         put3=(unsigned short *)&putTable[i].rgb;
  157.         get3=(unsigned short *)&getTable[i].rgb;
  158.         putSum[i%3]+=put3[i%3];
  159.         for(j=0;j<3;j++)getSum[j][i%3]+=get3[j];
  160.     }
  161.     isGray=!TestDeviceAttribute(card->device,gdDevType);
  162.     quickly=((flags&testClutQuicklyFlag)!=0);
  163.     clut=&card->depth[card->d].clut[quickly][isGray];
  164.     for(i=0;i<3;i++)for(j=0;j<3;j++){
  165.         clut->read.rgbGain[j][i]=(double)getSum[j][i]/putSum[i];
  166.         if(clut->read.rgbGain[j][i]!=floor(clut->read.rgbGain[j][i]))
  167.             clut->read.rgbGain[j][i]+=0.5*tableSize/putSum[i];
  168.     }
  169.     DisposePtr((Ptr)putTable);
  170.     DisposePtr((Ptr)getTable);
  171.     return error;
  172. }
  173.  
  174. OSErr WriteAndReadClut(GDHandle device,short flags,ColorSpec **put,ColorSpec **get)
  175. {
  176.     short i,clutSize,tableSize;
  177.     ColorSpec spec,*putTable,*getTable;
  178.     VBLTaskAndA5 vbl;
  179.     int error=0;
  180.  
  181.     clutSize=GDClutSize(device);
  182.  
  183.     // MAKE TABLE FOR CLUT
  184.     *put=putTable=MakeClutTable(device,flags);
  185.     if(putTable==NULL)return MemError();
  186.     tableSize=GetPtrSize((Ptr)putTable)/sizeof(putTable[0]);
  187.     
  188.     // MAKE BLANK TABLE
  189.     *get=getTable=(ColorSpec *)NewPtr(GetPtrSize((Ptr)putTable));
  190.     if(getTable==NULL)return MemError();
  191.     spec.value=spec.rgb.red=spec.rgb.green=spec.rgb.blue=0;
  192.     for(i=0;i<tableSize;i++)getTable[i]=spec;
  193.  
  194.     // WRITE CLUT & READ BACK, ONE CLUT-FULL AT A TIME
  195.     vbl.subroutine=NULL;                // request default subroutine
  196.     error=VBLInstall(&vbl,device,1000);
  197.     if(error)PrintfExit("%s line %d. WriteAndReadClut:VBLInstall error %d\n"
  198.         ,__FILE__,__LINE__,error);
  199.     vbl.vbl.vblCount=1;    // enable the interrupt service routine
  200.     for(i=0;i<tableSize;i+=clutSize){
  201.         error=WriteClut(device,&putTable[i],flags);
  202.         if(error)break;
  203.         vbl.newFrame=0;
  204.         if(WAIT_FOR_VBL)while(vbl.newFrame==0) ;    // wait until clut's been loaded before reading it
  205.         error=GDGetEntries(device,0,clutSize-1,&getTable[i]);    // Fixed 3/4/96
  206.         if(error)break;
  207.     }
  208.     VBLRemove(&vbl);
  209.     return error;
  210. }
  211.  
  212. OSErr ComputeRMSClutError(FILE *file
  213.     ,ColorSpec *putTable,ColorSpec *getTable,short flags,VideoInfo *card)
  214. {
  215.     short i,j,k,clutSize,tableSize,quickly,isGray,integralGains;
  216.     unsigned short *put3,*get3,n[3],bad;
  217.     double e,squaredError[3],model[3];
  218.     VideoCardClutTest *clut;
  219.     int error=0;
  220.         
  221.     isGray=!TestDeviceAttribute(card->device,gdDevType);
  222.     quickly=((flags&testClutQuicklyFlag)!=0);
  223.     clut=&card->depth[card->d].clut[quickly][isGray];
  224.     clutSize=GDClutSize(card->device);
  225.     tableSize=GetPtrSize((Ptr)putTable)/sizeof(putTable[0]);
  226.  
  227.     // Estimate error of linear model
  228.     for(i=0;i<3;i++){
  229.         squaredError[i]=0;
  230.         n[i]=0;
  231.     }
  232.     bad=0;
  233.     // Allow error of a few least significant steps of dac
  234.     clut->read.tolerance=2.9*(1<<(16-GDDacSize(card->device)));
  235.     for(i=0;i<tableSize;i++){
  236.         put3=(unsigned short *)&putTable[i].rgb;
  237.         get3=(unsigned short *)&getTable[i].rgb;
  238.         for(j=0;j<3;j++){
  239.             model[j]=put3[0]*clut->read.rgbGain[j][0]
  240.                 +put3[1]*clut->read.rgbGain[j][1]
  241.                 +put3[2]*clut->read.rgbGain[j][2];
  242.             e=get3[j]-model[j];
  243.             bad|=fabs(e)>clut->read.tolerance;
  244.             squaredError[j]+=e*e;
  245.             n[j]++;
  246.         }
  247.     }
  248.     for(i=0;i<3;i++){
  249.         clut->read.rgbError[i]=sqrt(squaredError[i]/n[i]);
  250.         if(!(flags&testClutSeriallyFlag))clut->read.rgbErrorAtOnce[i]=sqrt(squaredError[i]/n[i]);
  251.     }
  252.  
  253.     clut->read.identity=1;
  254.     e=1.999*pow(2,-GDDacSize(card->device));    // allow nonsignificant bits to be garbage
  255.     for(i=0;i<3;i++)for(j=0;j<3;j++)clut->read.identity&=(fabs(clut->read.rgbGain[i][j]-(i==j))<e);
  256.     
  257.     if(bad){
  258.         clut->read.errors=1;
  259.         if(!(flags&testClutSeriallyFlag))clut->read.errorsAtOnce=1;
  260.     }
  261.     if(bad){
  262.         // Print one-line error message to file.
  263.         fprintf(file,"\n");
  264.         if(flags&testClutQuicklyFlag)fprintf(file,"SetEntriesQuickly != cscGetEntries. ");
  265.         else switch((*card->device)->gdType){
  266.         case fixedType:
  267.             break;
  268.         case clutType:
  269.             fprintf(file,"cscSetEntries != cscGetEntries. ");
  270.             break;
  271.         case directType:
  272.             fprintf(file,"cscDirectSetEntries != cscGetEntries. ");
  273.             break;
  274.         }
  275.         fprintf(file,"%d-bit ",(int)card->depth[card->d].pixelSize);
  276.         if(isGray)fprintf(file,"gray pixels.");
  277.         else fprintf(file,"color pixels.");
  278.         if(flags&testClutSeriallyFlag)fprintf(file," Loaded one clut entry at a time.\n");
  279.         else fprintf(file," Loaded whole clut at once.\n");
  280.  
  281.         // Print the estimated color transformation matrix
  282.         fprintf(file,"\"expected\" assumes the best-fit linear color transformation matrix:\n");
  283.         integralGains=1;
  284.         for(j=0;j<3;j++)for(k=0;k<3;k++)
  285.                 integralGains&=(clut->read.rgbGain[j][k]==floor(clut->read.rgbGain[j][k]));
  286.         for(j=0;j<3;j++){
  287.             fprintf(file," (%cOut±%4.1f%%)%c"
  288.                 ,"RGB"[j],clut->read.rgbError[j]*100./USHRT_MAX," = "[j]);
  289.             if(integralGains)fprintf(file,"(%1.0f %1.0f %1.0f)"
  290.                 ,clut->read.rgbGain[j][0],clut->read.rgbGain[j][1],clut->read.rgbGain[j][2]);
  291.             else fprintf(file,"(%3.2f %3.2f %3.2f)"
  292.                 ,clut->read.rgbGain[j][0],clut->read.rgbGain[j][1],clut->read.rgbGain[j][2]);
  293.             fprintf(file,"%c(%cIn)\n"," x "[j],"RGB"[j]);
  294.         }
  295.         
  296.         // Print each clut error.
  297.         for(i=0;i<tableSize;i++){
  298.             put3=(unsigned short *)&putTable[i].rgb;
  299.             get3=(unsigned short *)&getTable[i].rgb;
  300.             bad=0;
  301.             for(j=0;j<3;j++){
  302.                 model[j]=put3[0]*clut->read.rgbGain[j][0]
  303.                     +put3[1]*clut->read.rgbGain[j][1]
  304.                     +put3[2]*clut->read.rgbGain[j][2];
  305.                 e=get3[j]-model[j];
  306.                 bad|=fabs(e)>clut->read.tolerance;
  307.             }
  308.             if(0 && bad)fprintf(file,"Clut[%3d] wrote(%04u,%04u,%04u) "
  309.                 "expected(%04.0f,%04.0f,%04.0f) but read(%04u,%04u,%04u)\n"
  310.                 ,i%clutSize,put3[0],put3[1],put3[2]
  311.                 ,model[0],model[1],model[2]
  312.                 ,get3[0],get3[1],get3[2]);
  313.             if(bad)fprintf(file,"Clut[%3d] wrote(%04x,%04x,%04x) "
  314.                 "expected(%04x,%04x,%04x) but read(%04x,%04x,%04x)\n"
  315.                 ,i%clutSize,put3[0],put3[1],put3[2]
  316.                 ,(unsigned short)(0.5+model[0]),(unsigned short)(0.5+model[1]),(unsigned short)(0.5+model[2])
  317.                 ,get3[0],get3[1],get3[2]);
  318.         }
  319.         fprintf(file,"\n");
  320.     }
  321.     clut->read.tested=1;
  322.     return error;
  323. }
  324.  
  325. OSErr GDTestClutVisually(short flags,VideoInfo *card)
  326. {
  327.     int error;
  328.     short clutSize,quickly,isGray;
  329.     ColorSpec *putTable,*normalTable;
  330.     Boolean weirdError,normalError;
  331.     VideoCardClutTest *clut;
  332.     
  333.     isGray=!TestDeviceAttribute(card->device,gdDevType);
  334.     quickly=((flags&testClutQuicklyFlag)!=0);
  335.     clut=&card->depth[card->d].clut[quickly][isGray];
  336.     if(clut->visual.tested==0){
  337.         clut->visual.errors=0;
  338.         clut->visual.errorsAtOnce=0;
  339.     }
  340.     if(card->device==NULL)return 0;
  341.     clutSize=GDClutSize(card->device);
  342.     normalTable=((**(**(**card->device).gdPMap).pmTable)).ctTable;
  343.     if(card->device==GetMainDevice())
  344.         putTable=MakeClutTable(card->device,testClutNegativeFlag);
  345.     else putTable=MakeClutTable(card->device,flags);
  346.     if(putTable==NULL)return MemError();
  347.     GDSaveGamma(card->device);
  348.     GDUncorrectedGamma(card->device);
  349.     error=WriteClut(card->device,putTable,flags);
  350.     if(error){
  351.         GDRestoreGamma(card->device);
  352.         GDRestoreDeviceClut(card->device);
  353.         return error;
  354.     }
  355.     printf(BLANKLINE);
  356.     printf("Screen should be weirdly colored; watch for subtle change as you hit return:" "\r");
  357.     while(getcharUnbuffered()==-1) ;
  358.     error=WriteClut(card->device,putTable,0);
  359.     if(error){
  360.         GDRestoreGamma(card->device);
  361.         GDRestoreDeviceClut(card->device);
  362.         return error;
  363.     }
  364.     printf(BLANKLINE);
  365.     printf("Did you see any change at all?");
  366.     weirdError=YesOrNo(0);
  367.     printf("\r");
  368.     error=WriteClut(card->device,normalTable,flags);
  369.     printf(BLANKLINE);
  370.     printf("Screen should be normal now; watch for subtle change as you hit return:" "\r");
  371.     while(getcharUnbuffered()==-1) ;
  372.     error=WriteClut(card->device,normalTable,0);
  373.     printf(BLANKLINE);
  374.     printf("Did you see any change at all?");
  375.     normalError=YesOrNo(0);
  376.     printf("\r");
  377.     GDRestoreGamma(card->device);
  378.     GDRestoreDeviceClut(card->device);
  379.     DisposePtr((Ptr)putTable);
  380.     clut->visual.tested++;
  381.     clut->visual.errors+=(weirdError || normalError);
  382.     if(!(flags&testClutSeriallyFlag))clut->visual.errorsAtOnce+=(weirdError || normalError);
  383.     return 0;
  384. }
  385.  
  386. ColorSpec *MakeClutTable(GDHandle device,short flags)
  387. {
  388.     short i,j,clutSize;
  389.     RGBColor put;
  390.     ColorSpec *table;
  391.     
  392.     clutSize=GDClutSize(device);
  393.     if((flags&testClutGains) && clutSize<16)clutSize=16;
  394.     table=(ColorSpec *)NewPtr(sizeof(*table)*clutSize);
  395.     if(table==NULL)return table;
  396.     for(i=0;i<clutSize;i++) {
  397.         if(flags&testClutNegativeFlag){
  398.             put=((**(**(**device).gdPMap).pmTable)).ctTable[clutSize-1-i].rgb;
  399.         }else if(flags&testClutGains){
  400.             // Estimate rgb gains of any transformation
  401.             put.red=put.green=put.blue=0;
  402.             j=(0xffffL*i+(clutSize-1)/2)/(clutSize-1);
  403.             switch(i%3){
  404.             case 0:
  405.                 put.red=j;
  406.                 break;
  407.             case 1:
  408.                 put.green=j;
  409.                 break;
  410.             case 2:
  411.                 put.blue=j;
  412.                 break;
  413.             }
  414.         }else if(flags&testClutLinearFlag){
  415.             // Linear test pattern
  416.             put.red=put.green=put.blue=0;
  417.             j=0xffffffff*(i+(clutSize-1)/2)/(clutSize-1);
  418.             switch(i%4){
  419.             case 0:
  420.                 put.red=put.green=put.blue=j;
  421.                 break;
  422.             case 1:
  423.                 put.red=j;
  424.                 break;
  425.             case 2:
  426.                 put.green=j;
  427.                 break;
  428.             case 3:
  429.                 put.blue=j;
  430.                 break;
  431.             }
  432.         }else{
  433.             // Random test pattern
  434.             put.red=randU();
  435.             put.green=randU();
  436.             put.blue=randU();
  437.         }
  438.         if(!SixteenBitGray){
  439.             put.red&=0xff00;
  440.             put.green&=0xff00;
  441.             put.blue&=0xff00;
  442.         }
  443.         table[i].rgb=put;
  444.     }
  445.     return table;
  446. }
  447.  
  448. OSErr WriteClut(GDHandle device,ColorSpec putTable[],short flags)
  449. {
  450.     short i,clutSize=GDClutSize(device);
  451.     char priority=7;
  452.     int error;
  453.     SetEntriesFunction function;
  454.  
  455.     if(flags&testClutQuicklyFlag)function=SetEntriesQuickly;
  456.     else function=GDSetEntriesByType;
  457.     if(flags&testClutSeriallyFlag){
  458.         // Load one clut entry at a time
  459.         for(i=0;i<clutSize;i++){
  460.             SwapPriority(&priority);    // Force driver to load clut now.
  461.             error=(function)(device,i,0,&putTable[i]);
  462.             SwapPriority(&priority);
  463.             if(error)return error;
  464.         }
  465.     }else{
  466.         // Load whole clut at once
  467.         SwapPriority(&priority);    // Force driver to load clut now.
  468.         error=(function)(device,0,clutSize-1,putTable);
  469.         SwapPriority(&priority);
  470.         if(error)return error;
  471.     }
  472.     return 0;
  473. }
  474.  
  475. OSErr GDTestClutHash(short flags,VideoInfo *card){
  476.     SetEntriesFunction function;
  477.     int error;
  478.     short isGray,quickly;
  479.     VideoCardClutTest *clut;
  480.     
  481.     if(card->device==NULL)return 0;
  482.     quickly=((flags&testClutQuicklyFlag)!=0);
  483.     isGray=!TestDeviceAttribute(card->device,gdDevType);
  484.     clut=&card->depth[card->d].clut[quickly][isGray];
  485.     if(clut->hash.tested==0)clut->hash.errors=0;
  486.     if(!clut->hash.doTest)return 0;
  487.     if(quickly)function=SetEntriesQuickly;
  488.     else function=GDSetEntriesByType;
  489.     error=VisibleHash(card->device,function,0,&clut->hash.errors);
  490.     clut->hash.tested=1;
  491.     return error;
  492. }
  493.  
  494. OSErr VisibleHash(GDHandle device,SetEntriesFunction function,short clutEntries
  495.     ,short *hashPtr)
  496. {
  497.     int error;
  498.     short clutSize;
  499.     short hash;
  500.     long tick;
  501.     ColorSpec *putTable,*linearTable=NULL;
  502.  
  503.     if(device==NULL || (**device).gdType==fixedType)return 0;
  504.     clutSize=GDClutSize(device);
  505.     if(clutEntries<0 || clutEntries>clutSize)return 1;
  506.     if(clutEntries==0)clutEntries=clutSize;
  507.     if((**device).gdType==directType){
  508.         if(function==GDSetEntries)function=GDDirectSetEntries;
  509.         putTable=linearTable=GDNewLinearColorTable(device);
  510.         if(linearTable==NULL)return MemError();
  511.     }else putTable=((**(**(**device).gdPMap).pmTable)).ctTable;
  512.     error=(function)(device,0,clutEntries-1,putTable);
  513.     if(!error){
  514.         printf(BLANKLINE);
  515.         if(device==GetMainDevice())
  516.             printf("Do you see any dynamic black specks on this screen? (No):");
  517.         else printf("Do you see any dynamic black specks on the test screen? (No):");
  518.         fflush(stdout);
  519.         do{
  520.             tick=TickCount();
  521.             do{
  522.                 (function)(device,0,clutEntries-1,putTable);
  523.             }while(TickCount()-tick<30);
  524.         }while(!kbhit());
  525.         hash=YesOrNo(0);
  526.         printf("\r");
  527.         if(linearTable!=NULL)DisposePtr((Ptr)linearTable);
  528.         if(hashPtr!=NULL)*hashPtr=hash;
  529.     }
  530.     return error;
  531. }
  532.  
  533. Boolean UnequalClutEntry(RGBColor *a,RGBColor *b,short mask)
  534. {
  535.     return (a->red&mask)!=(b->red&mask)
  536.         ||(a->green&mask)!=(b->green&mask)
  537.         ||(a->blue&mask)!=(b->blue&mask);
  538. }
  539.  
  540. /*
  541. When you set a video screen to monochrome or "gray" (as opposed to "color"),
  542. e.g. using the Control Panel:Monitors, the request is passed on to the video
  543. driver. The video driver transforms each of your rgb triplets to a
  544. luminance-equivalent gray, using a formula that must be very similar, if not
  545. equivalent, to the code below. The rounding is bad, e.g. any gray rgb triplet
  546. (i,i,i), other than (0,0,0), is transformed to (i-1,i-1,i-1), which is darker,
  547. failing to preserve luminance. However, my goal was to replicate Apple's crumby
  548. transformation, not to improve it. I presume that the reason that I have to trim
  549. my numbers down a tad (-0.00001) is that I'm doing this with 80-bit precision
  550. whereas the driver uses the 64-bit precision of the SANE routines. Presumably I
  551. could obtain the same result by compiling this subroutine separately, to use
  552. 64-bit floating point, since all the arguments are ints.
  553. */
  554. void RGBToGray(RGBColor *rgb,short dacSize)
  555. // Empirical formula to replicate Apple's luminance mapping.
  556. {
  557.     short i,shift;
  558.     unsigned long n;
  559.     
  560.     if(!SixteenBitGray){
  561.         shift=16-dacSize;
  562.         i=(rgb->red>>shift)*(0.30-0.00001)
  563.             +(rgb->green>>shift)*(0.59-0.00001)+(rgb->blue>>shift)*(0.11);
  564.         n=0xffffffffUL/((1<<dacSize)-1);
  565.         rgb->red=rgb->green=rgb->blue=(i*n)>>16;
  566.     }else{
  567.         rgb->red=rgb->green=rgb->blue=rgb->red*(0.30-0.00001)
  568.             +rgb->green*(0.59)+rgb->blue*(0.11-0.00001);
  569.     }
  570. }
  571.  
  572.